/*
 * Copyright (c) 2007-2008, Thomas Krause (tho.krause@gmail.com)
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of gidoo nor the names of its contributors may be used to
 *       endorse or promote products derived from this software without specific
 *       prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.gidoo.wickettools.xmledit;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.wicket.Component;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.gidoo.wickettools.WicketTools;
import org.gidoo.wickettools.xmledit.nodes.BlockNode;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.xml.sax.SAXException;

/**
 * A grapical XML-Editor component.
 * @author <a href="mailto:tho.krause@gmail.com>Thomas Krause</a>
 */
public class XMLEditor extends Panel
{
  
  public static final ResourceReference CSS_EDITOR =
    new ResourceReference(XMLEditor.class, "xmleditor.css");
  
  private File schemaFile;
  transient private SchemaTypeLoader schemaTypeLoader;
  
  private boolean isSimpleEditor;
  
  private Fragment fragXMLSource;
  private Fragment fragRichEdit;
  private CheckBox cbSource;
  
  // belongs to fragXMLSource
  private TextArea txtSource;
  
  // belongs to fragRichEdit
  private BlockNode rootBlockNode;
  
  // belongs to panel
  private FeedbackPanel feedback;
  private Form form;
    
  public XMLEditor(String id, File schemaFile)
  {
    super(id);
    
    feedback = new FeedbackPanel("feedback");
    feedback.setOutputMarkupId(true);
    add(feedback);
    
    this.schemaFile = schemaFile;
    
    this.schemaTypeLoader = getSchemaTypeForFile(schemaFile);
    
    if(schemaTypeLoader == null)
    {
      throw(new IllegalArgumentException("The schema file could not be compiled"));
    }
    
    form = new Form("form");
    form.setOutputMarkupId(true);
    add(form);
    
    // init fragXMLSource
    fragXMLSource = new Fragment("editor" ,"xmlSource", this);
    fragXMLSource.setOutputMarkupId(true);
    
    txtSource = new TextArea("txtSource", new Model(""));
    txtSource.setOutputMarkupId(true);
    fragXMLSource.add(txtSource);
    
    // init fragRichEdit
    fragRichEdit = new Fragment("editor", "richEditor", this);
    fragRichEdit.setOutputMarkupId(true);
    
    initRootBlockNode("");
    if(rootBlockNode == null)
    {      
      Element errorElem = new Element("error");
      rootBlockNode = new BlockNode("rootBlockNode", errorElem, schemaTypeLoader);
      error(getString("errorcreatingview"));

    }
    fragRichEdit.add(rootBlockNode);
    
    form.add(fragRichEdit);
    
    // init other components
    cbSource = new CheckBox("cbSource", 
      new PropertyModel(this, "isSimpleEditor"));
    
    cbSource.add(new AjaxFormSubmitBehavior("onchange") {

      @Override
      protected void onSubmit(AjaxRequestTarget target)
      {
        // property setters will be called
      }

      @Override
      protected void onError(AjaxRequestTarget target)
      {
        error("There was an error");
        target.addComponent(feedback);
      }
    });
    form.add(cbSource);
       
  }
  
  private void initRootBlockNode(String text)
  {
    try
    {      
      Schema schema = 
        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaFile);

      if(schemaTypeLoader == null)
      {
        schemaTypeLoader = getSchemaTypeForFile(schemaFile);
    
        if(schemaTypeLoader == null)
        {
          throw(new IllegalArgumentException("The schema file could not be compiled"));
        }
      }
      
      if(!"".equals(text))
      {
        schema.newValidator().validate(
          new StreamSource(new ByteArrayInputStream(text.getBytes())));
        
        Document doc = 
          new SAXBuilder(false).build(new ByteArrayInputStream(text.getBytes()));
        
        rootBlockNode = new BlockNode("rootBlockNode", doc.getRootElement(), schemaTypeLoader);
      }
      else
      {
        Element el = new Element(getString("empty"));
        rootBlockNode = new BlockNode("rootBlockNode", el, schemaTypeLoader);
      }
    }
    catch (SAXException ex)
    {
      Logger.getLogger(XMLEditor.class.getName()).log(Level.SEVERE, null, ex);
    }
    catch (IOException ex)
    {
      Logger.getLogger(XMLEditor.class.getName()).log(Level.SEVERE, null, ex);
    }
    catch (JDOMException ex)
    {
      Logger.getLogger(XMLEditor.class.getName()).log(Level.SEVERE, null, ex);
    }
    
  }
  
  private String createXMLTextFromRichEdit()
  {
    Document curDoc = new Document(rootBlockNode.constructElement());
    return new XMLOutputter(Format.getPrettyFormat()).outputString(curDoc);
  }
  
  /** The XML-content of this editor as text. */
  public String getText()
  {
    if(isSimpleEditor)
    {
      txtSource.setEscapeModelStrings(false);
      String result = txtSource.getModelObjectAsString();
      txtSource.setEscapeModelStrings(true);
      return result;
    }
    else
    {      
      return createXMLTextFromRichEdit();
    }
  }

  /** @see #getText */
  public void setText(String text)
  {
    if(isSimpleEditor)
    {
      txtSource.setModelObject(text);
      WicketTools.addToAjaxRequestTarget(new Component[] {txtSource}, this);
    }
    else
    {      
      initRootBlockNode(text);
      fragRichEdit.replace(rootBlockNode);
      WicketTools.addToAjaxRequestTarget(new Component[] {fragRichEdit}, this);
    }
    
    WicketTools.addToAjaxRequestTarget(new Component[] {feedback}, this);
    
  }

  /** 
   * Returns wether this XMLEditor is operating in the simple mode 
   * (just a textarea).
   */
  public boolean getIsSimpleEditor()
  {
    return isSimpleEditor;
  }

  /**
   * Use this to toggle between rich-edit and simple text editor functionality.
   * @param isSimpleEditor If false a visual javascript using editor, if true
   *                          a simple text-field.
   */
  public void setIsSimpleEditor(boolean isSimpleEditor)
  {
    
    boolean hasChanged = this.isSimpleEditor != isSimpleEditor;
    
    if(hasChanged)
    {
      if(isSimpleEditor)
      {
        txtSource.setModelObject(createXMLTextFromRichEdit());
        form.replace(fragXMLSource); 
      }
      else
      { 
        txtSource.setEscapeModelStrings(false);        
        initRootBlockNode(txtSource.getModelObjectAsString());
        txtSource.setEscapeModelStrings(true);
        form.replace(fragRichEdit);
        fragRichEdit.replace(rootBlockNode);
      }
    }
      
    this.isSimpleEditor = isSimpleEditor;
    
    WicketTools.addToAjaxRequestTarget(new Component[] {feedback, form}, this);
    
  }
  
  /** 
   * Returns a <code>SchemaTypeLoader</code> which is usefull to examinate
   * a XML Schema. Will return <code>null</code> if there was an exception or error.
   */
  public static SchemaTypeLoader getSchemaTypeForFile(File schemaFile)
  {
    try
    {
      XmlObject schemaAsObject = XmlObject.Factory.parse(schemaFile);
      SchemaTypeLoader l = XmlBeans.loadXsd(new XmlObject[]{schemaAsObject});
      return l;
    }
    catch (XmlException ex)
    {
      Logger.getLogger(XMLEditor.class.getName()).log(Level.SEVERE, null, ex);
    }
    catch (IOException ex)
    {
      Logger.getLogger(XMLEditor.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }
  
}
